﻿using System;
using System.Collections.Generic;
using Borg.Properties;

namespace Borg
{
    public class SettingStore : RuntimeElement, ISettingContainer
    {
        private readonly Dictionary<Plugin, OrderedDictionary<ExtensionType,
            SettingDictionary>> _pluginSettingDictionary =
            new Dictionary<Plugin, OrderedDictionary<ExtensionType, SettingDictionary>>();

        private readonly Dictionary<SettingDictionary, ExtensionType> _settingDictionaries =
            new Dictionary<SettingDictionary, ExtensionType>();

        private readonly HashSet<Plugin> _changedPluginSettings = new HashSet<Plugin>();

        private readonly SettingProvider _settingProvider;

        protected internal SettingProvider SettingProvider
        {
            get { return _settingProvider; }
        }

        protected internal SettingStore(Runtime runtime, SettingProvider settingProvider)
            : base(runtime)
        {
            _settingProvider = settingProvider;
        }

        public SettingDictionary GetSettings(ExtensionType extensionType)
        {
            if (!CheckAccess())
                throw GetAccessException();
            
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");
            
            return InternalGetSettings(extensionType);
        }

        public void SetSettings(ExtensionType extensionType, SettingDictionary settings)
        {
            if (!CheckAccess())
                throw GetAccessException();
            
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");
            
            if (settings == null)
                throw new ArgumentNullException("settings");
            
            if (settings.Parent != null)
                throw new ArgumentException(Exceptions.Setting_collection_already_added);
            
            if (!CheckAccess())
                throw GetAccessException();
            
            InternalSetSettings(extensionType, settings);
        }

        public bool RemoveSettings(ExtensionType extensionType)
        {
            if (!CheckAccess())
                throw GetAccessException();
            
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalRemoveSettings(extensionType);
        }

        public bool ContainsSettings(ExtensionType extensionType)
        {
            if (!CheckAccess())
                throw GetAccessException();
            
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");
            
            return InternalContainsSettings(extensionType);
        }

        protected internal virtual SettingDictionary InternalGetSettings(ExtensionType extensionType)
        {
            var pluginSettings = GetPluginSettings(extensionType.Plugin);

            if (pluginSettings.ContainsKey(extensionType))
                return pluginSettings[extensionType];

            var settings = new SettingDictionary();

            RegisterSettingDictionary(extensionType, settings);
            pluginSettings[extensionType] = settings;
            
            return settings;
        }

        protected internal virtual void InternalSetSettings(ExtensionType extensionType, SettingDictionary settings)
        {
            var pluginSettings = GetPluginSettings(extensionType.Plugin);

            if (pluginSettings.ContainsKey(extensionType))
            {
                var settingsDict = pluginSettings[extensionType];

                if (settings == settingsDict)
                    return;

                DeregisterSettingDictionary(settingsDict);
            }

            RegisterSettingDictionary(extensionType, settings);
            pluginSettings[extensionType] = settings;
        }

        protected internal virtual bool InternalRemoveSettings(ExtensionType extensionType)
        {
            var pluginSettings = GetPluginSettings(extensionType.Plugin);

            if (!pluginSettings.ContainsKey(extensionType))
                return false;

            DeregisterSettingDictionary(pluginSettings[extensionType]);
            
            return pluginSettings.Remove(extensionType);
        }

        protected internal virtual bool InternalContainsSettings(ExtensionType extensionType)
        {
            return InternalGetSettings(extensionType) != null;
        }

        protected internal virtual void OnSettingCollectionChanged(object sender, EventArgs e)
        {
            var plugin = _settingDictionaries[(SettingDictionary)sender].Plugin;

            if (_changedPluginSettings.Count == 0)
            {
                if (Runtime.Dispatcher.IsDisposed)
                {
                    _changedPluginSettings.Add(plugin);
                    SaveChangedSettings();

                    return;
                }
                
                Runtime.Dispatcher.BeginInvoke(new Action(SaveChangedSettings), new object[0]);
            }

            _changedPluginSettings.Add(plugin);
        }

        private OrderedDictionary<ExtensionType, SettingDictionary> GetPluginSettings(Plugin plugin)
        {
            if (_pluginSettingDictionary.ContainsKey(plugin))
                return _pluginSettingDictionary[plugin];

            var orderedDictionary =
                _pluginSettingDictionary[plugin] =
                SettingProvider.LoadSettings(plugin)
                ?? new OrderedDictionary<ExtensionType, SettingDictionary>();
            
            foreach (var pair in orderedDictionary)
                RegisterSettingDictionary(pair.Key, pair.Value);
            
            return orderedDictionary;
        }

        private void RegisterSettingDictionary(ExtensionType extensionType, SettingDictionary settings)
        {
            settings.Parent = this;
            settings.CollectionChanged += OnSettingCollectionChanged;
            _settingDictionaries.Add(settings, extensionType);
        }

        private void DeregisterSettingDictionary(SettingDictionary settings)
        {
            settings.Parent = null;
            settings.CollectionChanged -= OnSettingCollectionChanged;
            _settingDictionaries.Remove(settings);
        }

        private void SaveChangedSettings()
        {
            foreach (var plugin in _changedPluginSettings)
                SettingProvider.SaveSettings(plugin, _pluginSettingDictionary[plugin]);
            
            _changedPluginSettings.Clear();
        }
    }
}
